/**
 * @file lv_tabview.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_tabview.h"
#if LV_USE_TABVIEW

#include "../../../misc/lv_assert.h"

/*********************
 *      DEFINES
 *********************/
#define MY_CLASS    &lv_tabview_class

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void lv_tabview_constructor(const lv_obj_class_t* class_p,
                                   lv_obj_t* obj);
static void lv_tabview_destructor(const lv_obj_class_t* class_p, lv_obj_t* obj);
static void lv_tabview_event(const lv_obj_class_t* class_p, lv_event_t* e);
static void btns_value_changed_event_cb(lv_event_t* e);
static void cont_scroll_end_event_cb(lv_event_t* e);

/**********************
 *  STATIC VARIABLES
 **********************/
const lv_obj_class_t lv_tabview_class =
{
    .constructor_cb = lv_tabview_constructor,
    .destructor_cb = lv_tabview_destructor,
    .event_cb = lv_tabview_event,
    .width_def = LV_PCT(100),
    .height_def = LV_PCT(100),
    .base_class = &lv_obj_class,
    .instance_size = sizeof(lv_tabview_t)
};

static lv_dir_t tabpos_create;
static lv_coord_t tabsize_create;

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

lv_obj_t* lv_tabview_create(lv_obj_t* parent, lv_dir_t tab_pos,
                            lv_coord_t tab_size)
{
    LV_LOG_INFO("begin");
    tabpos_create = tab_pos;
    tabsize_create = tab_size;

    lv_obj_t* obj = lv_obj_class_create_obj(&lv_tabview_class, parent);
    lv_obj_class_init_obj(obj);
    return obj;
}

lv_obj_t* lv_tabview_add_tab(lv_obj_t* obj, const char* name)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_tabview_t* tabview = (lv_tabview_t*)obj;
    lv_obj_t* cont = lv_tabview_get_content(obj);

    lv_obj_t* page = lv_obj_create(cont);
    lv_obj_set_size(page, LV_PCT(100), LV_PCT(100));
    lv_obj_clear_flag(page, LV_OBJ_FLAG_CLICK_FOCUSABLE);
    uint32_t tab_id = lv_obj_get_child_cnt(cont);

    lv_obj_t* btns = lv_tabview_get_tab_btns(obj);

    const char** old_map = (const char**)tabview->map;
    const char** new_map;

    /*top or bottom dir*/
    if (tabview->tab_pos & LV_DIR_VER)
    {
        new_map = lv_mem_alloc((tab_id + 1) * sizeof(const char*));
        lv_memcpy_small(new_map, old_map, sizeof(const char*) * (tab_id - 1));
        new_map[tab_id - 1] = lv_mem_alloc(strlen(name) + 1);
        strcpy((char*)new_map[tab_id - 1], name);
        new_map[tab_id] = "";
    }
    /*left or right dir*/
    else
    {
        new_map = lv_mem_alloc((tab_id * 2) * sizeof(const char*));
        lv_memcpy_small(new_map, old_map, sizeof(const char*) * (tab_id - 1) * 2);

        if (tabview->tab_cnt == 0)
        {
            new_map[0] = lv_mem_alloc(strlen(name) + 1);
            strcpy((char*)new_map[0], name);
            new_map[1] = "";
        }
        else
        {
            new_map[tab_id * 2 - 3] = "\n";
            new_map[tab_id * 2 - 2] = lv_mem_alloc(strlen(name) + 1);
            new_map[tab_id * 2 - 1] = "";
            strcpy((char*)new_map[(tab_id * 2) - 2], name);
        }
    }

    tabview->map = new_map;
    lv_btnmatrix_set_map(btns, (const char**)new_map);
    lv_mem_free(old_map);

    lv_btnmatrix_set_btn_ctrl_all(btns,
                                  LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_CLICK_TRIG |
                                  LV_BTNMATRIX_CTRL_NO_REPEAT);

    tabview->tab_cnt++;

    if (tabview->tab_cnt == 1)
    {
        lv_tabview_set_act(obj, 0, LV_ANIM_OFF);
    }

    lv_btnmatrix_set_btn_ctrl(btns, tabview->tab_cur, LV_BTNMATRIX_CTRL_CHECKED);

    return page;
}

void lv_tabview_rename_tab(lv_obj_t* obj, uint32_t id, const char* new_name)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_tabview_t* tabview = (lv_tabview_t*)obj;

    if (id >= tabview->tab_cnt)
    {
        return;
    }

    if (tabview->tab_pos & LV_DIR_HOR)
    {
        id *= 2;
    }

    lv_mem_free((void*)tabview->map[id]);
    tabview->map[id] = lv_mem_alloc(strlen(new_name) + 1);
    strcpy((void*)tabview->map[id], new_name);
    lv_obj_invalidate(obj);
}

void lv_tabview_set_act(lv_obj_t* obj, uint32_t id, lv_anim_enable_t anim_en)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    if (obj->being_deleted)
    {
        return;
    }

    lv_tabview_t* tabview = (lv_tabview_t*)obj;

    if (id >= tabview->tab_cnt)
    {
        id = tabview->tab_cnt - 1;
    }

    /*To be sure lv_obj_get_content_width will return valid value*/
    lv_obj_update_layout(obj);

    lv_obj_t* cont = lv_tabview_get_content(obj);

    if (cont == NULL)
    {
        return;
    }

    if ((tabview->tab_pos & LV_DIR_VER) != 0)
    {
        lv_coord_t gap = lv_obj_get_style_pad_column(cont, LV_PART_MAIN);
        lv_coord_t w = lv_obj_get_content_width(cont);

        if (lv_obj_get_style_base_dir(obj, LV_PART_MAIN) != LV_BASE_DIR_RTL)
        {
            lv_obj_scroll_to_x(cont, id * (gap + w), anim_en);
        }
        else
        {
            int32_t id_rtl = -(int32_t)id;
            lv_obj_scroll_to_x(cont, (gap + w) * id_rtl, anim_en);
        }
    }
    else
    {
        lv_coord_t gap = lv_obj_get_style_pad_row(cont, LV_PART_MAIN);
        lv_coord_t h = lv_obj_get_content_height(cont);
        lv_obj_scroll_to_y(cont, id * (gap + h), anim_en);
    }

    lv_obj_t* btns = lv_tabview_get_tab_btns(obj);
    lv_btnmatrix_set_btn_ctrl(btns, id, LV_BTNMATRIX_CTRL_CHECKED);
    tabview->tab_cur = id;
}

uint16_t lv_tabview_get_tab_act(lv_obj_t* obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_tabview_t* tabview = (lv_tabview_t*)obj;
    return tabview->tab_cur;
}

lv_obj_t* lv_tabview_get_content(lv_obj_t* tv)
{
    return lv_obj_get_child(tv, 1);
}

lv_obj_t* lv_tabview_get_tab_btns(lv_obj_t* tv)
{
    return lv_obj_get_child(tv, 0);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

static void lv_tabview_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
{
    LV_UNUSED(class_p);
    lv_tabview_t* tabview = (lv_tabview_t*)obj;

    tabview->tab_pos = tabpos_create;

    switch (tabview->tab_pos)
    {
    case LV_DIR_TOP:
        lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN);
        break;

    case LV_DIR_BOTTOM:
        lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN_REVERSE);
        break;

    case LV_DIR_LEFT:
        lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW);
        break;

    case LV_DIR_RIGHT:
        lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW_REVERSE);
        break;
    }

    lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100));

    lv_obj_t* btnm;
    lv_obj_t* cont;

    btnm = lv_btnmatrix_create(obj);
    cont = lv_obj_create(obj);

    lv_btnmatrix_set_one_checked(btnm, true);
    tabview->map = lv_mem_alloc(sizeof(const char*));
    tabview->map[0] = "";
    lv_btnmatrix_set_map(btnm, (const char**)tabview->map);
    lv_obj_add_event_cb(btnm, btns_value_changed_event_cb, LV_EVENT_VALUE_CHANGED,
                        NULL);
    lv_obj_add_flag(btnm, LV_OBJ_FLAG_EVENT_BUBBLE);

    lv_obj_add_event_cb(cont, cont_scroll_end_event_cb, LV_EVENT_ALL, NULL);
    lv_obj_set_scrollbar_mode(cont, LV_SCROLLBAR_MODE_OFF);

    switch (tabview->tab_pos)
    {
    case LV_DIR_TOP:
    case LV_DIR_BOTTOM:
        lv_obj_set_size(btnm, LV_PCT(100), tabsize_create);
        lv_obj_set_width(cont, LV_PCT(100));
        lv_obj_set_flex_grow(cont, 1);
        break;

    case LV_DIR_LEFT:
    case LV_DIR_RIGHT:
        lv_obj_set_size(btnm, tabsize_create, LV_PCT(100));
        lv_obj_set_height(cont, LV_PCT(100));
        lv_obj_set_flex_grow(cont, 1);
        break;
    }

    lv_group_t* g = lv_group_get_default();

    if (g)
    {
        lv_group_add_obj(g, btnm);
    }

    if ((tabview->tab_pos & LV_DIR_VER) != 0)
    {
        lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW);
        lv_obj_set_scroll_snap_x(cont, LV_SCROLL_SNAP_CENTER);
    }
    else
    {
        lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);
        lv_obj_set_scroll_snap_y(cont, LV_SCROLL_SNAP_CENTER);
    }

    lv_obj_add_flag(cont, LV_OBJ_FLAG_SCROLL_ONE);
    lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
}

static void lv_tabview_destructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
{
    LV_UNUSED(class_p);
    lv_tabview_t* tabview = (lv_tabview_t*)obj;

    uint32_t i;

    if (tabview->tab_pos & LV_DIR_VER)
    {
        for (i = 0; i < tabview->tab_cnt; i++)
        {
            lv_mem_free((void*)tabview->map[i]);
            tabview->map[i] = NULL;
        }
    }

    if (tabview->tab_pos & LV_DIR_HOR)
    {
        for (i = 0; i < tabview->tab_cnt; i++)
        {
            lv_mem_free((void*)tabview->map[i * 2]);
            tabview->map[i * 2] = NULL;
        }
    }

    lv_mem_free(tabview->map);
    tabview->map = NULL;
}

static void lv_tabview_event(const lv_obj_class_t* class_p, lv_event_t* e)
{
    LV_UNUSED(class_p);
    lv_res_t res = lv_obj_event_base(&lv_tabview_class, e);

    if (res != LV_RES_OK)
    {
        return;
    }

    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t* target = lv_event_get_target(e);

    if (code == LV_EVENT_SIZE_CHANGED)
    {
        lv_tabview_set_act(target, lv_tabview_get_tab_act(target), LV_ANIM_OFF);
    }
}

static void btns_value_changed_event_cb(lv_event_t* e)
{
    lv_obj_t* btns = lv_event_get_target(e);

    lv_obj_t* tv = lv_obj_get_parent(btns);
    uint32_t id = lv_btnmatrix_get_selected_btn(btns);
    lv_tabview_set_act(tv, id, LV_ANIM_OFF);
}

static void cont_scroll_end_event_cb(lv_event_t* e)
{
    lv_obj_t* cont = lv_event_get_target(e);
    lv_event_code_t code = lv_event_get_code(e);

    lv_obj_t* tv = lv_obj_get_parent(cont);
    lv_tabview_t* tv_obj = (lv_tabview_t*)tv;

    if (code == LV_EVENT_LAYOUT_CHANGED)
    {
        lv_tabview_set_act(tv, lv_tabview_get_tab_act(tv), LV_ANIM_OFF);
    }
    else if (code == LV_EVENT_SCROLL_END)
    {
        lv_indev_t* indev = lv_indev_get_act();

        if (indev && indev->proc.state == LV_INDEV_STATE_PRESSED)
        {
            return;
        }

        lv_point_t p;
        lv_obj_get_scroll_end(cont, &p);

        lv_coord_t t;

        if ((tv_obj->tab_pos & LV_DIR_VER) != 0)
        {
            lv_coord_t w = lv_obj_get_content_width(cont);

            if (lv_obj_get_style_base_dir(tv, LV_PART_MAIN) == LV_BASE_DIR_RTL)
            {
                t = -(p.x - w / 2) / w;
            }
            else
            {
                t = (p.x + w / 2) / w;
            }
        }
        else
        {
            lv_coord_t h = lv_obj_get_content_height(cont);
            t = (p.y + h / 2) / h;
        }

        if (t < 0)
        {
            t = 0;
        }

        bool new_tab = false;

        if (t != lv_tabview_get_tab_act(tv))
        {
            new_tab = true;
        }

        lv_tabview_set_act(tv, t, LV_ANIM_ON);

        if (new_tab)
        {
            lv_event_send(tv, LV_EVENT_VALUE_CHANGED, NULL);
        }
    }
}
#endif /*LV_USE_TABVIEW*/
